package ua.khpi.si.kit49.diploma.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ua.khpi.si.kit49.diploma.core.Path;
import ua.khpi.si.kit49.diploma.db.dto.Connection;
import ua.khpi.si.kit49.diploma.util.PathComparator;

/**
 * Implementation of path service
 * 
 * @author netstalk3r
 * 
 */
@Component("pathService")
public class PathServiceImpl implements PathService {

	/**
	 * logger
	 */
	private static final Logger log = Logger.getLogger(PathServiceImpl.class);

	// Constants
	private static final Integer NEXT_POINT_IN_PATH = 1;
	private static final Integer DIRECT_CONNECTION = 1;


	private Integer amountOfVertex;

	// array with ties
	private static int[][] map;

	private int[] road;// route - numbers of points in the graph
	private boolean[] incl;// incl[i]=true, if point with number i included in
							// road

	private List<Path> allPaths;
	private static Boolean mapEmpty = true;

	public PathServiceImpl() {
		super();
		allPaths = new ArrayList<Path>();
	}

	@Autowired(required = true)
	private ConnectionService connectionService;

	public Boolean isMapEmpty() {
		return mapEmpty;
	}

	@Override
	public List<Path> findPaths(Integer vertex, Integer start, Integer finish) {
		log.info("Path service - start.");
		allPaths.clear();
		this.amountOfVertex = vertex;
		log.debug("Amount of vertex - " + this.amountOfVertex);
		Integer realStart = start - 1;
		Integer readFinish = finish - 1;
		log.debug("real start - " + realStart + "; real finish - " + readFinish
				+ ";");
		log.debug("is map empty: " + mapEmpty);
		if (mapEmpty) {
			log.info("the map is empty; filling the map");
			fillMap(getAllConnection());
			mapEmpty = false;
		}
		road = new int[amountOfVertex];
		incl = new boolean[amountOfVertex];
		for (int i = 0; i < amountOfVertex; i++) {
			road[i] = 0;
			incl[i] = false;
		}
		road[0] = start;
		incl[realStart] = true;
		step(realStart, readFinish, NEXT_POINT_IN_PATH);
		log.debug("Amount of paths - " + allPaths.size());
		PathComparator pathComparator = new PathComparator();
		Collections.sort(allPaths, pathComparator);
		log.info(allPaths);
		log.info("Path service - finish.");
		return allPaths;
	}

	private List<Connection> getAllConnection() {
		log.debug("receiving all connections");
		return connectionService.getAllConnection();
	}

	/** 
	 * @param listOfConnection
	 */
	private void fillMap(List<Connection> listOfConnection) {
		if (amountOfVertex.equals(null)) {
			throw new NullPointerException("Amount of vertex didn't establish");
		}
		log.info("filling start");
		map = new int[amountOfVertex][amountOfVertex];
		for (Connection conn : listOfConnection) {
			map[conn.getCityStart().getId() - 1][conn.getCityFinish().getId() - 1] = DIRECT_CONNECTION;
		}
		StringBuilder mapRow = new StringBuilder();
		for (int i = 0; i < amountOfVertex; i++) {
			for (int j = 0; j < amountOfVertex; j++) {
				mapRow.append(map[i][j]);
			}
			log.debug(mapRow);
			mapRow.setLength(0);
		}
		log.info("filling finish");
	}

	/**
	 * 
	 * @param start
	 *            start point to step
	 * @param finish
	 *            finish point in route
	 * @param nextPoint
	 *            number of next point
	 */
	private void step(int start, int finish, int nextPoint) {
		if (start == finish) {
			// point coincided
			Path path = new Path();
			for (int i = 0; i < nextPoint; i++) {
				path.addPoint(road[i]);
			}
			allPaths.add(path);
		} else {
			// choose the next point
			for (int i = 0; i < amountOfVertex; i++) {
				if ((map[start][i] != 0) && (!incl[i])) {
					// the point is connected to the current and not included in
					// route
					road[nextPoint] = i + 1;// add vertex in the path
					incl[i] = true; // mark the vertex

					step(i, finish, nextPoint + 1);
					incl[i] = false;
					road[nextPoint] = 0;
				}
			}
		}
	}

}
